'''
Created on 24/07/2009

@author: eh01
'''
import datetime
import os
import unittest

from datastore.database import *
from datastore.uri import Uri

TEST_DATABASE = 'databasetest.datastore'

class DataStoreTest(unittest.TestCase):

    def setUp(self):
        pass

    def tearDown(self):
        # removes the database file after a test is run
        os.remove(TEST_DATABASE)


    def test_put_case_01(self):
        """[database] Test the resource is saved successfully """
        ds = DataStore(TEST_DATABASE)
        resource1 = Resource()
        ds.put(resource1)
        if not resource1.id:
            self.fail('Save failed')

    def test_put_case_02(self):
        """[database] Test saving two resources successfully """
        ds = DataStore(TEST_DATABASE)
        resource1 = Resource()
        resource2 = Resource()
        ds.put([resource1, resource2])
        if not resource1.id and not resource2.id:
            self.fail('Save failed')
        
    def test_put_case_03(self):
        """[database] Test saving with invalid type """
        ds = DataStore(TEST_DATABASE)
        dt = datetime.datetime.now()
        try:
            ds.put(dt)
        except TypeError:
            assert True
        else:
            self.fail('Should not be able to put a datetime object')
        
    def test_put_case_04(self):
        """[database] Test saving with two instances of the DataStore """
        ds_1 = DataStore(TEST_DATABASE)
        ds_2 = DataStore(TEST_DATABASE)
        resource1 = Resource()
        resource2 = Resource()
        ds_1.put(resource1)
        ds_2.put(resource2)
        
        # check ids have been assigned
        if not resource1.id and not resource2.id:
            self.fail('Save failed')        
        
    def test_put_case_05(self):
        """[database] Test updating a resource """
        key = Uri('dummykey')
        value = Uri('dummyvalue')
        attributevalue = 'dummyattributevalue'
        
        ds = DataStore(TEST_DATABASE)
        resource1 = Resource()
        
        # save resource
        ds.put(resource1)
        
        # change resource metadata and attributes
        resource1.set_metadata(key, value)
        resource1.attribute = attributevalue
        
        # update resource
        ds.put(resource1)
        resource2 = ds.gets(resource1.id)[0]
        
        # checks
        if resource1.id == "1":
            try:
                if resource2.attribute != attributevalue:
                    self.fail("Object did not update")
            except KeyError:
                self.fail("Object did not update")
            try:
                if resource2.get_single_metadata(key) != value:
                    self.fail("Metadata did not update")
            except KeyError:
                self.fail("Metadata did not update")                
        else:
            self.fail("New instance shouldn't be created")
    
    def test_put_case_06(self):
        """[database] Test updating two resources successfully """
        key1 = Uri('key1')
        value1 = Uri('value1')
        key2 = Uri('key2')
        value2 = Uri('value2')
        value3 = 'value3'
        ds = DataStore(TEST_DATABASE)
        resource1 = Resource()
        resource1.set_metadata(key1, value1)
        resource2 = Resource()
        resource2.attribute1 = value2
        
        # save resources
        ds.put([resource1, resource2])
        
        # change resource metadata and attributes
        resource1.set_metadata(key1, value2)
        resource1.set_metadata(key2, value3)
        resource1.attribute1 = value1
        resource2.set_metadata(key2, value1)
        resource2.attribute2 = value2
        
        # update resource
        ds.put([resource1, resource2])
        
        # retrieve updated resources
        resources = ds.gets([resource1.id, resource2.id])
        if resources[0].id == "1":
            rs1 = resources[0]
            rs2 = resources[1]
        else:
            rs2 = resources[0]
            rs1 = resources[1]
        
        # check we have assigned the resources correctly before we test updates based on this assignment
        self.assertEqual(rs1.id, "1")
        self.assertEqual(rs2.id, "2")
        
        # checks both resources have been updated
        self.assertEqual(rs1.get_metadata(key1), [value1, value2])
        self.assertEqual(rs1.get_metadata(key2), [value3])
        self.assertEqual(rs1.attribute1, value1)
        self.assertEqual(rs2.get_metadata(key2), [value1])
        self.assertEqual(rs2.attribute1, value2)
        self.assertEqual(rs2.attribute2, value2)

    def test_put_case_07(self):
        """[database] Test updating two instances with different DataStore instances """
        key1 = Uri('key1')
        value1 = 'value1'
        key2 = Uri('key2')
        value2 = 'value2'
        value3 = 'value3'        
        ds_1 = DataStore(TEST_DATABASE)
        ds_2 = DataStore(TEST_DATABASE)
        resource1 = Resource()
        resource1.set_metadata(key1, value1)
        resource2 = Resource()
        resource2.attribute1 = value1
        
        self.assertEqual(resource1.get_single_metadata(key1), value1)
        self.assertEqual(resource2.attribute1, value1)
        
        # save resources in different datastore instances
        ds_1.put([resource1])
        ds_2.put([resource2])

        # change resource metadata and attributes
        resource2.attribute1 = value2
        resource1.set_metadata(key1, value2)
        resource1.set_metadata(key2, value3)
        resource1.attribute1 = value1
        resource2.set_metadata(key2, value1)
        resource2.attribute2 = value2

        # now update resource in the other datastore instance        
        ds_1.put([resource2])
        ds_2.put([resource1])
                
        # retrieve both resources from the first datastore
        resources = ds_1.gets([resource1.id, resource2.id])
        
        # make sure rs1 == id1 and rs2 == id2
        if resources[0].id == "1":
            rs1 = resources[0]
            self.assertEqual(resources[1].id, "2")
            rs2 = resources[1]
        else:
            self.assertEqual(resources[0].id, "2")
            self.assertEqual(resources[1].id, "1")
            rs1 = resources[1]
            rs2 = resources[0]
        
        # checks both resources have been updated
        self.assertEqual(rs1.get_metadata(key1), [value1, value2])
        self.assertEqual(rs1.attribute1, value1)
        self.assertEqual(rs1.get_metadata(key2), [value3])
        self.assertEqual(rs2.attribute1, value2)
        self.assertEqual(rs2.get_single_metadata(key2), value1)
        self.assertEqual(rs2.attribute2, value2)        

    def test_get_case_01(self):
        """[database] Test getting the resource with valid id """
        ds = DataStore(TEST_DATABASE)
        resource1 = Resource()
        ds.put([resource1])
        resources = ds.gets("1")
        if len(resources) == 1 and resources[0] == resource1:
            assert True
        else:
            self.fail('Resource return is incorrect')
    
    def test_get_case_02(self):
        """[database] Test getting the resource with invalid id """
        ds = DataStore(TEST_DATABASE)
        try:
            ds.gets("aaa")
        except NotFoundException:
            assert True
        else:
            self.fail('gets should have failed as "aaa" id should not exist')  
        
    def test_get_case_03(self):
        """[database] Test getting the resource with valid ids """
        ds = DataStore(TEST_DATABASE)
        resource1 = Resource()
        resource2 = Resource()
        ds.put([resource1, resource2])
        resources = ds.gets(["1", "2"])
        if len(resources) == 2 and resource1 in resources and resource2 in resources:
            assert True
        else:
            self.fail('Resources return is incorrect')
        
    def test_get_case_04(self):
        """[database] Test getting the resource with one invalid id in ids """
        ds = DataStore(TEST_DATABASE)
        resource1 = Resource()
        resource2 = Resource()
        ds.put([resource1, resource2])
        try:
            ds.gets(["1", "-1"])
        except NotFoundException:
            assert True
        else:
            self.fail('should fail will non valid id (-1)')        
    
    def test_delete_case_01(self):
        """[database] Test deleting persisted resource """
        ds = DataStore(TEST_DATABASE)
        resource1 = Resource()
        ds.put(resource1)
        resource2 = ds.gets(resource1.id)
        if resource1 == resource2[0]:
            ds.delete(resource1)
            try:
                # try to retrieve it to make sure its been deleted
                ds.gets(resource1.id)
            except NotFoundException:
                assert True
            else:
                self.fail('Delete Failed')
        else:
            self.fail("Put or Get failed")
        
    def test_delete_case_02(self):
        """[database] Test deleting non-persisted resource """
        ds = DataStore(TEST_DATABASE)
        resource1 = Resource()
        # should we throw an error if we try to delete a resource that is not persisted?
        ds.delete(resource1)
        
    
    def test_delete_case_03(self):
        """[database] Test deleting invalid type """
        ds = DataStore(TEST_DATABASE)
        try:
            ds.delete([datetime.datetime.now()])
        except TypeError:
            assert True
        else:
            self.fail('Should fail with wrong type')
        
    def test_find_case_01(self):
        """[database] Test find resource with key and value """
        key = Uri('dummykey')
        value = 'dummyvalue'
        ds = DataStore(TEST_DATABASE)
        resource1 = Resource()
        resource1.set_metadata(key, value)
        ds.put([resource1])
        resources = ds.find([(key,value)])
        if len(resources) == 1 and resources[0] == resource1:
            assert True
        else:
            self.fail('Resource return is incorrect')
    
    def test_find_case_02(self):
        """[database] Test find resource with key and no value """
        key = Uri('dummykey')
        value = 'dummyvalue'
        ds = DataStore(TEST_DATABASE)
        resource1 = Resource()
        resource1.set_metadata(key, value)
        ds.put([resource1])
        resources = ds.find([(key, None)])
        if len(resources) == 1 and resources[0] == resource1:
            assert True
        else:
            self.fail('Resource return is incorrect')
        
    def test_find_case_03(self):
        """[database] Test find resource with no matching key and value combination """
        key = Uri('dummykey')
        value = 'dummyvalue'
        ds = DataStore(TEST_DATABASE)
        try:
            ds.find([(key,value)])
        except NotFoundException:
            assert True
        else:
            self.fail('Find should fail as no Resources match the key and value')
        
    def test_find_case_04(self):
        """[database] Test find resource with no matching key """
        key = Uri('dummykey')
        ds = DataStore(TEST_DATABASE)
        try:
            ds.find([(key, None)])
        except NotFoundException:
            assert True
        else:
            self.fail('Find should fail as no Resources match the key')

    def test_find_case_05(self):
        """[database] Test find resource with multiple key1 and value1 """
        key1 = Uri('key1')
        value1 = 'value1'
        key2 = Uri('key2')
        value2 = 'value2'
        ds = DataStore(TEST_DATABASE)
        resource1 = Resource()
        resource1.set_metadata(key1, value1)
        resource1.set_metadata(key2, value2)        
        resource2 = Resource()
        resource2.set_metadata(key2, value2)
        ds.put([resource1, resource2])
        resources = ds.find([(key1,value1), (key2,value2)])
        if len(resources) == 1 and resource1 in resources:
            assert True
        else:
            self.fail('Resource return is incorrect')
    
    def test_find_case_06(self):
        """[database] Tests find resource with multiple key and key and value """
        key1 = Uri('key1')
        value1 = 'value1'
        key2 = Uri('key2')
        value2 = 'value2'
        ds = DataStore(TEST_DATABASE)
        resource1 = Resource()
        resource1.set_metadata(key1, value1)
        resource1.set_metadata(key2, value2)        
        resource2 = Resource()
        resource2.set_metadata(key2, value2)
        ds.put([resource1, resource2])
        resources = ds.find([(key1,value1), (key2,None)])
        if len(resources) == 1 and resource1 in resources:
            assert True
        else:
            self.fail('Resource return is incorrect')

    def test_find_case_07(self):
        """[database] Tests find resource with one key that returns multiple resources """
        key1 = Uri('key1')
        value1 = 'value1'
        value2 = 'value2'
        ds = DataStore(TEST_DATABASE)
        resource1 = Resource()
        resource1.set_metadata(key1, value1)
        resource2 = Resource()
        resource2.set_metadata(key1, value2)
        ds.put([resource1, resource2])
        resources = ds.find([(key1,None)])
        if len(resources) == 2 and resource1 in resources and resource2 in resources:
            assert True
        else:
            self.fail('Resource return is incorrect')

    def test_find_case_08(self):
        """[database] Tests find resource with one key that returns multiple resources """
        key1 = Uri('key1')
        value1 = 'value1'
        key2 = Uri('key2')
        value2 = 'value2'
        ds = DataStore(TEST_DATABASE)
        resource1 = Resource()
        resource1.set_metadata(key1, value1)
        resource2 = Resource()
        resource2.set_metadata(key1, value2)
        ds.put([resource1, resource2])
        try:
            ds.find([(key1,None), (key2,None)])
        except NotFoundException:
            assert True
        else:
            self.fail('Should not find key2')            

    def test_find_case_09(self):
        """[database] Tests find resource with multiple keys that return one resource """
        key1 = Uri('key1')
        value1 = Uri('value1')
        key2 = Uri('key2')
        value2 = 'value2'
        ds = DataStore(TEST_DATABASE)
        resource1 = Resource()
        resource1.set_metadata(key1, value1)
        resource1.set_metadata(key2, value2)
        ds.put([resource1])
        resources = ds.find([(key1,None), (key2,None)])
        if len(resources) == 1 and resource1 in resources:
            assert True
        else:
            self.fail('Resource return is incorrect')

                    
    def test_find_case_10(self):
        """[database] Test find resource with invalid key type """
        ds = DataStore(TEST_DATABASE)
        try:
            ds.find(datetime.datetime.now())
        except TypeError:
            assert True
        else:
            self.fail('Datetime type should not be allowed to be used as a key_value list')
            
    def test_find_case_11(self):
        """[database] Test find resource with key and value and that resource has the ns information to expand uris """
        key = Uri('internal:dummykey')
        value = 'dummyvalue'
        ds = DataStore(TEST_DATABASE)
        resource1 = Resource()
        resource1.set_metadata(key, value)
        ds.put([resource1])
        resources = ds.find([(Uri('http://node.internal/dummykey'),value)])
        if len(resources) == 1 and resources[0] == resource1:
            # Test metadata on the resource can expand its own uri to match the one passed in
            self.assertEqual(resources[0].get_single_metadata(Uri('http://node.internal/dummykey')), value)
        else:
            self.fail('Resource return is incorrect')            

    def test_find_case_12(self):
        """[database] Tests find resource with None for key and value - wildcard. """
        key1 = Uri('key1')
        value1 = 'value1'
        key2 = Uri('key2')
        value2 = 'value2'
        ds = DataStore(TEST_DATABASE)
        resource1 = Resource()
        resource1.set_metadata(key1, value1)
        resource2 = Resource()
        resource2.set_metadata(key1, value2)
        ds.put([resource1, resource2])
        resources = ds.find([(None, None)])
        self.assertTrue(len(resources) == 2)
        
    def test_find_type_case_01(self):
        """[database] Test find_type resource with key and value """
        value = 'dummyvalue'
        ds = DataStore(TEST_DATABASE)
        resource1 = Resource()
        resource1.set_type(value)
        ds.put([resource1])
        resources = ds.find_type(value)
        if len(resources) == 1 and resources[0] == resource1:
            assert True
        else:
            self.fail('Resource return for find_type is incorrect')
        
    def test_delete_put_case_01(self):
        '''[database] Test delete and put a resource in the same db transaction'''
        ds = DataStore(TEST_DATABASE)
        resource1 = Resource()
        resource2 = Resource()
        resource3 = Resource()
        ds.put([resource1, resource2])
        self.assertTrue(len(ds.gets([resource1.id, resource2.id])) == 2)
        ds.delete_put([resource1], [resource3])
        self.assertTrue(ds.gets([resource3.id]))
        self.assertRaises(NotFoundException, ds.gets, [resource1.id])
    
if __name__ == "__main__":
    #import sys;sys.argv = ['', 'Test.test_']
    unittest.main()